/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.imagepipeline.platform;

import com.facebook.common.internal.Preconditions;
import com.facebook.common.internal.VisibleForTesting;
import com.facebook.common.references.CloseableReference;
import com.facebook.common.streams.LimitedInputStream;
import com.facebook.common.streams.TailAppendingInputStream;
import com.facebook.imagepipeline.bitmaps.SimpleBitmapReleaser;
import com.facebook.imagepipeline.image.EncodedImage;
import com.facebook.imagepipeline.memory.BitmapPool;
import com.facebook.imageutils.JfifUtil;
import com.facebook.imagepipeline.annotation.Nullable;
import com.facebook.imagepipeline.annotation.ThreadSafe;
import com.facebook.imagepipeline.util.Pools;
import com.oszc.bbhmlibrary.utils.LogUtil;
import ohos.agp.utils.Color;
import ohos.agp.utils.Rect;
import ohos.media.image.ImageSource;
import ohos.media.image.PixelMap;
import ohos.media.image.common.*;

import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;

/**
 * Bitmap decoder for ART VM (Lollipop and up).
 */
@ThreadSafe
public abstract class DefaultDecoder implements PlatformDecoder {

    private static final Class<?> TAG = DefaultDecoder.class;

    private Size reuseSize= new Size();

    /**
     * Size of temporary array. Value recommended by docs for decoding Bitmaps.
     */
    private static final int DECODE_BUFFER_SIZE = 16 * 1024;

    private final BitmapPool mBitmapPool;

    private final @Nullable
    PreverificationHelper mPreverificationHelper;

    {
        mPreverificationHelper = new PreverificationHelper();
    }

    /**
     * ArtPlatformImageDecoder decodes images from InputStream - to do so we need to provide temporary
     * buffer, otherwise framework will allocate one for us for each decode request
     */
    @VisibleForTesting
    final Pools.SynchronizedPool<ByteBuffer> mDecodeBuffers;

    // TODO (5884402) - remove dependency on JfifUtil
    private static final byte[] EOI_TAIL =
            new byte[]{(byte) JfifUtil.MARKER_FIRST_BYTE, (byte) JfifUtil.MARKER_EOI};

    public DefaultDecoder(BitmapPool bitmapPool, int maxNumThreads, Pools.SynchronizedPool decodeBuffers) {
        mBitmapPool = bitmapPool;
        mDecodeBuffers = decodeBuffers;
        for (int i = 0; i < maxNumThreads; i++) {
            mDecodeBuffers.release(ByteBuffer.allocate(DECODE_BUFFER_SIZE));
        }
    }

    @Override
    public CloseableReference<PixelMap> decodeFromEncodedImage(
            EncodedImage encodedImage, PixelFormat bitmapConfig, @Nullable Rect regionToDecode) {
        return decodeFromEncodedImageWithColorSpace(encodedImage, bitmapConfig, regionToDecode, null);
    }

    @Override
    public CloseableReference<PixelMap> decodeJPEGFromEncodedImage(
            EncodedImage encodedImage,
            PixelFormat bitmapConfig,
            @Nullable Rect regionToDecode,
            int length) {
        return decodeJPEGFromEncodedImageWithColorSpace(
                encodedImage, bitmapConfig, regionToDecode, length, null);
    }

    /**
     * Creates a bitmap from encoded bytes.
     *
     * @param encodedImage   the encoded image with a reference to the encoded bytes
     * @param bitmapConfig   the  used to create the decoded
     *                       Bitmap
     * @param regionToDecode optional image region to decode or null to decode the whole image
     * @param colorSpace     the target color space of the decoded bitmap, must be one of the named color
     *                       space in . If null, then SRGB color space is
     *                       assumed if the SDK version >= 26.
     * @return the bitmap
     * @throws OutOfMemoryError if the Bitmap cannot be allocated
     */
    @Override
    public CloseableReference<PixelMap> decodeFromEncodedImageWithColorSpace(
            EncodedImage encodedImage,
            PixelFormat bitmapConfig,
            @Nullable Rect regionToDecode,
            @Nullable final ColorSpace colorSpace) {
        final ImageSource.DecodingOptions options = getDecodeOptionsForStream(encodedImage, bitmapConfig);
        boolean retryOnFail = options.desiredPixelFormat != PixelFormat.ARGB_8888;
        try {
            return decodeFromStream(encodedImage.getInputStream(), options, regionToDecode, colorSpace);
        } catch (RuntimeException re) {
            if (retryOnFail) {
                return decodeFromEncodedImageWithColorSpace(
                        encodedImage, PixelFormat.ARGB_8888, regionToDecode, colorSpace);
            }
            throw re;
        }
    }

    /**
     * Creates a bitmap from encoded JPEG bytes. Supports a partial JPEG image.
     *
     * @param encodedImage   the encoded image with reference to the encoded bytes
     *                       Bitmap
     * @param regionToDecode optional image region to decode or null to decode the whole image
     * @param length         the number of encoded bytes in the buffer
     * @param colorSpace     the target color space of the decoded bitmap, must be one of the named color
     *                       assumed if the SDK version >= 26.
     * @return the bitmap
     * @throws OutOfMemoryError if the Bitmap cannot be allocated
     */
    @Override
    public CloseableReference<PixelMap> decodeJPEGFromEncodedImageWithColorSpace(
            EncodedImage encodedImage,
            PixelFormat bitmapConfig,
            @Nullable Rect regionToDecode,
            int length,
            @Nullable final ColorSpace colorSpace) {
        boolean isJpegComplete = encodedImage.isCompleteAt(length);

        LogUtil.error(TAG.getSimpleName(),"region decodeJPEGFromEncodedImageWithColorSpace B isJpegComplete:"+isJpegComplete);

        final ImageSource.DecodingOptions options = getDecodeOptionsForStream(encodedImage, bitmapConfig);

        InputStream jpegDataStream = encodedImage.getInputStream();
        // At this point the InputStream from the encoded image should not be null since in the
        // pipeline,this comes from a call stack where this was checked before. Also this method needs
        // the InputStream to decode the image so this can't be null.
        Preconditions.checkNotNull(jpegDataStream);
        if (encodedImage.getSize() > length) {
            LogUtil.error(TAG.getSimpleName(),"region decodeJPEGFromEncodedImageWithColorSpace C");
            jpegDataStream = new LimitedInputStream(jpegDataStream, length);
        }
        if (!isJpegComplete) {
            LogUtil.error(TAG.getSimpleName(),"region decodeJPEGFromEncodedImageWithColorSpace D");
            jpegDataStream = new TailAppendingInputStream(jpegDataStream, EOI_TAIL);
        }
        boolean retryOnFail = options.desiredPixelFormat != PixelFormat.ARGB_8888;
        try {
            LogUtil.error(TAG.getSimpleName(),"region decodeJPEGFromEncodedImageWithColorSpace E");
            return decodeFromStream(jpegDataStream, options, regionToDecode, colorSpace);
        } catch (RuntimeException re) {
            if (retryOnFail) {
                return decodeJPEGFromEncodedImageWithColorSpace(
                        encodedImage, PixelFormat.ARGB_8888, regionToDecode, length, colorSpace);
            }
            throw re;
        }
    }

    /**
     * This method is needed because of dependency issues.
     *
     * @param inputStream    the InputStream
     * @param regionToDecode optional image region to decode or null to decode the whole image
     * @return the bitmap
     */
    protected CloseableReference<PixelMap> decodeStaticImageFromStream(
            InputStream inputStream, ImageSource.DecodingOptions options, @Nullable Rect regionToDecode) {
        return decodeFromStream(inputStream, options, regionToDecode, null);
    }

    /**
     * Create a bitmap from an input stream.
     *
     * @param inputStream    the InputStream
     * @param regionToDecode optional image region to decode or null to decode the whole image
     * @param colorSpace     the target color space of the decoded bitmap, must be one of the named color
     *                       assumed if the SDK version >= 26.
     * @return the bitmap
     */
    private CloseableReference<PixelMap> decodeFromStream(
            InputStream inputStream,
            ImageSource.DecodingOptions options,
            @Nullable Rect regionToDecode,
            @Nullable final ColorSpace colorSpace) {
        LogUtil.error(TAG.getSimpleName(),"---- decodeFromStream start ----");
        if (inputStream == null) throw new NullPointerException();
        int targetWidth = options.desiredSize.width;
        int targetHeight = options.desiredSize.height;
        if (regionToDecode != null) {
            targetWidth = regionToDecode.getWidth() / options.sampleSize;
            targetHeight = regionToDecode.getHeight() / options.sampleSize;
        }
        if(options.sampleSize > 1){
            options.desiredSize = null;
        }
        LogUtil.error(TAG.getSimpleName(),String.format("decodeFromStream targetWidth:%d , targetHeight:%d",targetWidth,targetHeight));
        @Nullable PixelMap bitmapToReuse = null;
        boolean shouldUseHardwareBitmapConfig =
                mPreverificationHelper != null
                        && mPreverificationHelper.shouldUseHardwareBitmapConfig(PixelFormat.ARGB_8888);
        if (regionToDecode == null && shouldUseHardwareBitmapConfig) {
            // Cannot reuse bitmaps with Bitmap.Config.HARDWARE
            options.editable = false;
        } else {
            if (regionToDecode != null && shouldUseHardwareBitmapConfig) {
                // If region decoding was requested we need to fallback to default config
                options.desiredPixelFormat = PixelFormat.ARGB_8888;
            }
            final int sizeInBytes = getBitmapSize(targetWidth, targetHeight, options);
            bitmapToReuse = mBitmapPool.get(sizeInBytes);
            if (bitmapToReuse == null) {
                throw new NullPointerException("BitmapPool.get returned null");
            }
        }
        // inBitmap can be nullable
        //noinspection ConstantConditions
        //options.inBitmap = bitmapToReuse;
        // Performs transformation at load time to target color space.
        options.desiredColorSpace = colorSpace == null ? ColorSpace.SRGB : colorSpace;

        PixelMap decodedBitmap = null;
        ByteBuffer byteBuffer = mDecodeBuffers.acquire();
        if (byteBuffer == null) {
            byteBuffer = ByteBuffer.allocate(DECODE_BUFFER_SIZE);
        }
        try {
            ImageSource.SourceOptions sourceOptions = new ImageSource.SourceOptions();
            PixelMap pixelMap = ImageSource.create(inputStream,sourceOptions).createPixelmap(options);
            PixelMap.InitializationOptions initializationOptions = new PixelMap.InitializationOptions();
            initializationOptions.editable = options.editable;
            initializationOptions.pixelFormat = options.desiredPixelFormat;
            initializationOptions.releaseSource = true;
            initializationOptions.useSourceIfMatch = false;
            reuseSize.width = targetWidth;
            reuseSize.height = targetHeight;
            initializationOptions.size = reuseSize;
            //initializationOptions.scaleMode = ScaleMode.CENTER_CROP;
            if (regionToDecode != null) {
                //initializationOptions.size = new Size(targetWidth,targetHeight);
                decodedBitmap = PixelMap.create(
                        pixelMap,
                        new ohos.media.image.common.Rect(0,0,targetWidth, targetHeight),
                        initializationOptions);
            }else {
                decodedBitmap = PixelMap.create(pixelMap,initializationOptions);
            }
            int[] buffer = new int[targetWidth * targetHeight];
            decodedBitmap.readPixels(buffer,0,targetWidth,new ohos.media.image.common.Rect(0,0,targetWidth,targetHeight));
            //LogUtil.error(TAG.getSimpleName(),"decodeFromStream bimtpap format:"+ bitmapToReuse.getImageInfo().pixelFormat.name()+"   decodemap format:"+decodedBitmap.getImageInfo().pixelFormat+"   option.pixelFormat:"+options.desiredPixelFormat.name());
            //LogUtil.error(TAG.getSimpleName(),"decodeFromStream decodedBimtpap size:"+ decodedBitmap.getPixelBytesNumber() +"  bitmapresue size:"+bitmapToReuse.getPixelBytesCapacity());
            bitmapToReuse.resetConfig(new Size(targetWidth,targetHeight),PixelFormat.RGB_565); //ARGB888 RGB565
            bitmapToReuse.setAlphaType(AlphaType.UNPREMUL);
            LogUtil.error(TAG.getSimpleName(),"decodeFromStream after config  bitmapresue size:"+bitmapToReuse.getPixelBytesNumber());
            bitmapToReuse.writePixels(buffer,0,targetWidth,new ohos.media.image.common.Rect(0,0,targetWidth,targetHeight));
            //LogUtil.error(TAG.getSimpleName(),"decodeFromStream B");
        } catch (IllegalArgumentException | NullPointerException e) {
            LogUtil.error(TAG.getSimpleName(),"decodeFromStream  errror:"+e.getMessage());
            e.printStackTrace();
            if (bitmapToReuse != null) {
                mBitmapPool.release(bitmapToReuse);
            }
            // This is thrown if the Bitmap options are invalid, so let's just try to decode the bitmap
            // as-is, which might be inefficient - but it works.
            try {
                // We need to reset the stream first
                inputStream.reset();

                // Bitmap naiveDecodedBitmap = BitmapFactory.decodeStream(inputStream);
                ImageSource.SourceOptions sourceOptions = new ImageSource.SourceOptions();
                ImageSource.DecodingOptions opts = new ImageSource.DecodingOptions();
                ImageSource imageSource = ImageSource.create(inputStream, sourceOptions);
                PixelMap naiveDecodedBitmap = imageSource.createPixelmap(opts);
                if (naiveDecodedBitmap == null) {
                    throw e;
                }
                return CloseableReference.of(naiveDecodedBitmap, SimpleBitmapReleaser.getInstance());
            } catch (IOException re) {
                LogUtil.error(TAG.getSimpleName(),"decodeFromStream  errror:"+re.getMessage());
                // We throw the original exception instead since it's the one causing this workaround in the
                // first place.
                throw e;
            }
        } catch (RuntimeException re) {
            LogUtil.error(TAG.getSimpleName(),"decodeFromStream  errror:"+re.getMessage());
            if (bitmapToReuse != null) {
                mBitmapPool.release(bitmapToReuse);
            }
            throw re;
        } finally {
            mDecodeBuffers.release(byteBuffer);
        }

        // If bitmap with Bitmap.Config.HARDWARE was used, `bitmapToReuse` will be null and it's
        // expected
        /*
       if (bitmapToReuse != null && bitmapToReuse != decodedBitmap) {
            mBitmapPool.release(bitmapToReuse);
            decodedBitmap.release();
            throw new IllegalStateException();
        }
         */

        LogUtil.error(TAG.getSimpleName(),"---- decodeFromStream end ----");
        return CloseableReference.of(bitmapToReuse, mBitmapPool);
    }

    /**
     * Options returned by this method are configured with mDecodeBuffer which is GuardedBy("this")
     */
    private static ImageSource.DecodingOptions getDecodeOptionsForStream(
            EncodedImage encodedImage, PixelFormat bitmapConfig) {
        ImageSource.DecodingOptions decodingOptions = new ImageSource.DecodingOptions();
        decodingOptions.sampleSize = encodedImage.getSampleSize();
        decodingOptions.editable = true;
        decodingOptions.desiredPixelFormat = bitmapConfig;
        ImageSource imageSource = ImageSource.create(encodedImage.getInputStream(), new ImageSource.SourceOptions());
        PixelMap pixelMap = imageSource.createPixelmap(decodingOptions);
        ImageInfo imageInfo = pixelMap.getImageInfo();
        decodingOptions.desiredSize = new Size(imageInfo.size.width/decodingOptions.sampleSize, imageInfo.size.height/decodingOptions.sampleSize);
        if (decodingOptions.desiredSize.width == -1 || decodingOptions.desiredSize.height == -1) {
            throw new IllegalArgumentException();
        }

        pixelMap.release();


    /*final BitmapFactory.Options options = new BitmapFactory.Options();
    // Sample size should ONLY be different than 1 when downsampling is enabled in the pipeline
    options.inSampleSize = encodedImage.getSampleSize();
    options.inJustDecodeBounds = true;
    // fill outWidth and outHeight
    BitmapFactory.decodeStream(encodedImage.getInputStream(), null, options);
    if (options.outWidth == -1 || options.outHeight == -1) {
      throw new IllegalArgumentException();
    }
    options.inJustDecodeBounds = false;
    options.inDither = true;
    options.inPreferredConfig = bitmapConfig;
    options.inMutable = true;*/
        return decodingOptions;
    }

    public abstract int getBitmapSize(
            final int width, final int height, final ImageSource.DecodingOptions options);
}
